# Table of Contents

# taskAffinity, launchMode

taskAffinitylaunchMode를 이해하기 위해 다음과 같은 프로젝트를 생성합시다.

세 개의 액티비티로 구성된 앱이 있습니다. Activity A에서 버튼을 누르면 Activity B로 화면이 전환됩니다. Activity B에서 버튼을 누르면 Activity C로 화면이 전환됩니다.

코드는 다음과 같습니다.

class ActivityA : AppCompatActivity() {

    val button: Button by lazy { findViewById<Button>(R.id.activity_a_button) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_a)

        button.setOnClickListener {
            val intent = Intent(this, ActivityB::class.java)
            startActivity(intent)
        }
    }
}
class ActivityB : AppCompatActivity() {

    val button: Button by lazy { findViewById<Button>(R.id.activity_b_button) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_bb)

        button.setOnClickListener {
            val intent = Intent(this, ActivityC::class.java)
            startActivity(intent)
        }
    }
}
class ActivityC : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.c)
    }
}

AndroidManifest.xml은 다음과 같습니다.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.yologger.activity">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Activity">
        <activity android:name=".ActivityA">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".ActivityB">
        </activity>
        <activity android:name=".ActivityC">
        </activity>
    </application>

</manifest>

이제 앱을 실행하고 Activity A > Activity B > Activity C 순서로 호출합시다. 그리고 ADB를 사용하여 태스크의 스택을 출력해봅시다. 터미널에서 아래 명령어를 입력하면 됩니다.

$ adb shell dumpsys activity activities

이제 출력 결과를 확인해봅시다. 출력 결과가 매우 길기 때문에 중요한 부분만 살펴보겠습니다.

ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)
Display #0 (activities from top to bottom):
    ...
    * Task{ ... A=10719:com.yologger.activity U=0 StackId=28904 sz=3}
        ...    
      * Hist #2: ActivityRecord{2eef99a u0 com.yologger.activity/.ActivityC t28904}
        ... 
      * Hist #1: ActivityRecord{b4dbaed u0 com.yologger.activity/.ActivityB t28904}
        ... 
      * Hist #0: ActivityRecord{bdd0c7e u0 com.yologger.activity/.ActivityA t28904}
    ... 

com.yologger.activity라는 태스크에 세 개의 액티비티가 쌓여있는 것을 확인할 수 있습니다. 태스크 이름을 별도로 설정하지 않으면 자동으로 패키지 이름으로 설정됩니다.

# taskAffinity

taskAffinity속성을 사용하면 태스크 이름을 직접 설정할 수 있습니다. AndroidManifest.xml에서 루트 액티비티인 ActivityA의 taskAffinity속성을 설정해봅시다.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.yologger.activity">

    <application>
        <activity android:name=".ActivityA"
            android:taskAffinity="com.yologger.my_task">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        ...
    </application>

</manifest>

태스크의 스택을 다시 출력해봅시다.

ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)
Display #0 (activities from top to bottom):
    ...
    * Task{ ... A=10719:com.yologger.my_task U=0 StackId=28904 sz=3}
        ...    
      * Hist #2: ActivityRecord{2eef99a u0 com.yologger.activity/.ActivityC t28904}
        ... 
      * Hist #1: ActivityRecord{b4dbaed u0 com.yologger.activity/.ActivityB t28904}
        ... 
      * Hist #0: ActivityRecord{bdd0c7e u0 com.yologger.activity/.ActivityA t28904}
    ... 

태스크의 이름이 com.yologger.my_task로 변경된 것을 확인할 수 있습니다.

# 의문점

주목할 것이 하나 있습니다. 위 예제에서는 Activity B와 Activity C에는 별도의 속성을 지정하지 않고, Activity A에만 taskAffinity속성을 지정했습니다. 그런대도 Activity B와 Activity C가 동일한 이름의 태스크에 추가되었습니다.

만약 Activity B와 Activity C는 또 다른 태스크에서 실행하려면 어떻게 해야할까요? 이러한 경우 launchMode를 사용할 수 있습니다.

# launchMode

AndroidManifest.xmllaunchMode속성을 사용하면 태스크와 액티비티 실행의 흐름을 제어할 수 있습니다.

<activity 
    android:name="your_activity_name"
    android:launchMode="singleTask">
</activity>

launchMode는 다음과 같이 네 개의 값 중 하나를 설정할 수 있습니다.

  • singleTask
  • singleInstance
  • multiple
  • singleTop

# singleTask

android:launchMode="singleTask"는 새로운 태스크를 생성하고 그 곳에 액티비티를 추가합니다. 주의할 점은 새로운 태스크의 이름을 taskAffinity속성으로 설정해주어야 한다는 것입니다. taskAffinity속성을 설정하지 않으면 새로운 액티비티를 호출하는 액티비티의 태스크에 추가됩니다.

위 예제에서 Activity C에 launchMode속성과 taskAffinity속성을 추가해봅시다.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.yologger.activity">

    <application>
        <activity android:name=".ActivityA"
            android:taskAffinity="com.yologger.task1">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter></activity>

        <activity android:name=".ActivityB">
        </activity>

        <activity android:name=".ActivityC"
            android:taskAffinity="com.yologger.task2"
            android:launchMode="singleTask">
        </activity>
    </application>

</manifest>

다시 태스크의 스택을 확인해봅시다.

ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)
Display #0 (activities from top to bottom):
  ...
    * Task{ ... A=10719:com.yologger.task2 }
      * Hist #0: ActivityRecord{260ef2c u0 com.yologger.activity/.ActivityC t28915}
    * Task{ ... A=10719:com.yologger.task1 }
      * Hist #1: ActivityRecord{656d934 u0 com.yologger.activity/.ActivityB t28914}
      * Hist #0: ActivityRecord{8320723 u0 com.yologger.activity/.ActivityA t28914}

Activity C는 com.yologger.task2라는 새로운 태스크에서 실행되었습니다.

현재 실행 중인 태스크가 Task 2일 때는 Task 1 전체가 background 상태로 진입합니다.

최근 앱 목록(Recent List)에서도 두 개의 태스크를 확인할 수 있습니다.

주의할 점이 하나 있습니다. Task 1과 Task 2가 둘 다 실행된 상태에서 Activity C를 다시 호출하면, 새로운 Task 3가 생성되지 않고 기존의 Task 2를 재사용합니다.

또한 Activity C에서는 onCreate()가 호출되지 않고 onNewIntent()가 호출됩니다.

이제 Activity D를 추가하고 다음과 같이 launchModetaskAffinity를 설정해봅시다.

<?xml version="1.0" encoding="utf-8"?>
<manifest 
xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.yologger.activity">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Activity">
        <activity
            android:name=".ActivityA"
            android:taskAffinity="com.yologger.task1">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".ActivityB">
        </activity>
        <activity
            android:name=".ActivityC"
            android:taskAffinity="com.yologger.task2"
            android:launchMode="singleTask">
        </activity>
        <activity
            android:name=".ActivityD"
            android:taskAffinity="com.yologger.task2"
            android:launchMode="singleTask">
        </activity>
    </application>
</manifest>

이제 Activity A > Activity B > Activity C > Activity D 순서대로 실행하면 다음과 같이 스택과 액티비티가 생성됩니다.

# singleInstance

바로 위 예제에서 Activity C와 Activity D의 launchModesingleInstance로 바꿔봅시다.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.yologger.activity">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Activity">
        <activity
            android:name=".ActivityA"
            android:taskAffinity="com.yologger.task1">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".ActivityB"></activity>
        <activity
            android:name=".ActivityC"
            android:taskAffinity="com.yologger.task2"
            android:launchMode="singleInstance"></activity>
        <activity
            android:name=".ActivityD"
            android:taskAffinity="com.yologger.task2"
            android:launchMode="singleInstance"></activity>
    </application>
</manifest>

다시 Activity A > Activity B > Activity C > Activity D 순서대로 실행하면 다음과 같이 스택과 액티비티가 생성됩니다.

singleInstancesingleTask와 매우 유사합니다. 다만 차이점은 새로운 태스크 안에 오직 하나의 루트 액티비티만을 유지한다는 것입니다.

# standard

standardsingleTop은 새로운 태스크를 만드는 것과는 관련이 없습니다. 기존 태스크에 새로운 액티비티를 어떻게 추가할 것인지와 관련있습니다.

standard는 기본값입니다. 실행할 액티비티를 기존 태스크의 가장 위에 추가합니다. 또한 동일한 액티비티의 인스턴스를 여러 개 추가할 수 있습니다.

다음과 같이 두 개의 액티비티(MainActivity, SubActivity)가 있다고 가정합시다.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.yologger.activity">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Activity">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity 
            android:name=".SubActivity"
            android:launchMode="standard" />
    </application>

</manifest>

이제 MainActivity > SubActivity > SubActivity > SubActivity 순서대로 호출하면, 태스크 상태는 다음과 같습니다.

# singleTop

바로 위 예제에서 standardsingleTop으로 변경합시다.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.yologger.activity">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Activity">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity 
            android:name=".SubActivity"
            android:launchMode="singleTop" />
    </application>

</manifest>

다시 MainActivity > SubActivity > SubActivity > SubActivity 순서대로 호출하면, 태스크 상태는 다음과 같습니다.

singleTop은 실행할 액티비티의 인스턴스가 이미 스택의 맨 위에 있으면 새로운 인스턴스를 생성하지 않고 기존 인스턴스를 사용합니다. 이때 호출되는 액티비티에서는 onCreate()가 아니라 onNewIntent()가 호출됩니다.

class SecondActivity : AppCompatActivity() {

    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
    }
}